#ifndef DICTIONARY_DICTIONARY_PARSER_H
#define DICTIONARY_DICTIONARY_PARSER_H

#include <map>


#include <xercesc/sax2/SAX2XMLReader.hpp>
#include <xercesc/sax2/XMLReaderFactory.hpp>

#include <iostream>
#include <cstring>

#include <xercesc/sax2/DefaultHandler.hpp>
#include <boost/lexical_cast.hpp>

#include "Document.h"
#include "Grammar.h"

namespace Dictionary
{
	XERCES_CPP_NAMESPACE_USE

	template <typename T = std::string>
	class DictionaryParser : public DefaultHandler
	{
		 public:
			 DictionaryParser(const std::string &xml_to_parse) : xml_to_parse(xml_to_parse),is_element_started(false) { this->init(); }
			 DictionaryParser() : xml_to_parse(""),is_element_started(false) { this->init(); }
			~DictionaryParser() {}

			void set_xml_to_parse(const std::string &);

			Document<T> * parse();
			//DefaultHandler
			virtual void startElement(const   XMLCh* const uri,const XMLCh* const localname,const XMLCh* const qname,const Attributes& attrs);
			virtual void fatalError(const SAXParseException& exception);
			virtual void endElement(const XMLCh *const uri,const XMLCh *const localname,const XMLCh *const qname);
			virtual void characters(const XMLCh *const chars,const unsigned int	length);
		private:
			DictionaryParser(const DictionaryParser<T> &);
			DictionaryParser & operator=(const DictionaryParser<T> &);
			void init() { current_node = NULL ; current_section = NULL ; current_sentence = NULL; }

			void add_node(long ,const Node<T> &);
			
			char *copy_string(char *);

			//xml specific help functions for parsing
			void set_state(const Attributes& attrs);
			void end_state(char *element); 

			bool set_document_properties(char *data);
			bool set_node_properties(char *data);
			bool set_section_properties(char *data);
			bool set_sentence_properties(char *data);

			std::string xml_to_parse;
			bool is_element_started;
			char *current_element;
			Grammar::State current_state;
			Document<T> *document;

			Node<T> *current_node;
			Section<T> *current_section;

			Sentence<T> *current_sentence;
	};

	template <typename T>
	inline void DictionaryParser<T>::set_xml_to_parse(const std::string &xml_to_parse)
	{
		this->xml_to_parse = xml_to_parse;
	}
}

template <typename T>
void Dictionary::DictionaryParser<T>::add_node(long id, const Dictionary::Node<T> &node)
{
	document->add_node(node);
}


template <typename T>
void Dictionary::DictionaryParser<T>::startElement(const   XMLCh* const    uri,
                            const   XMLCh* const    localname,
                            const   XMLCh* const    qname,
                            const   Attributes&     attrs)
{
	
    char* element = XMLString::transcode(localname);
		this->is_element_started = true;
		this->current_element = this->copy_string(element);
    XMLString::release(&element);

		this->set_state(attrs);
}

template <typename T>
void Dictionary::DictionaryParser<T>::endElement(const XMLCh *const uri,const XMLCh *const localname,const XMLCh *const qname)
{
	char * locname = XMLString::transcode(localname);
	this->end_state(locname);
	XMLString::release(&locname);
	this->is_element_started = false;
}

template <typename T>
void Dictionary::DictionaryParser<T>::fatalError(const SAXParseException& exception)
{
    char* message = XMLString::transcode(exception.getMessage());
		std::cout << "Fatal Error: " << message << " at line: " << exception.getLineNumber() << std::endl;
		XMLString::release(&message);
}

template <typename T>
void Dictionary::DictionaryParser<T>::characters(const XMLCh *const chars,const unsigned int	length)
{
	if (is_element_started == false) return;
	
  char* data = XMLString::transcode(chars);
	
	this->set_document_properties(data);
	this->set_node_properties(data);
	this->set_section_properties(data);
	this->set_sentence_properties(data);

	XMLString::release(&data);
}

template <typename T>
Dictionary::Document<T> * Dictionary::DictionaryParser<T>::parse()
{

	try {
        XMLPlatformUtils::Initialize();
       }
       catch (const XMLException& toCatch) {
            char* message = XMLString::transcode(toCatch.getMessage());
						std::cout << "Error during initialization! :\n";
            std::cout << "Exception message is: \n" << message << "\n";
            XMLString::release(&message);
            return NULL;
        }

			  const char* xmlFile = this->xml_to_parse.c_str();
        SAX2XMLReader* parser = XMLReaderFactory::createXMLReader();
        parser->setFeature(XMLUni::fgSAX2CoreValidation, false);   // optional
        parser->setFeature(XMLUni::fgSAX2CoreNameSpaces, false);   // optional

        parser->setContentHandler(this);
        parser->setErrorHandler(this);

        try {
					  document = new Document<>();
            parser->parse(xmlFile);
        }
        catch (const XMLException& toCatch) {
            char* message = XMLString::transcode(toCatch.getMessage());
            std::cout << "Exception message is: \n" << message << "\n";
            XMLString::release(&message);
            return NULL;
        }
        catch (const SAXParseException& toCatch) {
            char* message = XMLString::transcode(toCatch.getMessage());
            std::cout << "Exception message is: \n" << message << "\n";
            XMLString::release(&message);
            return NULL;
        }
				catch(const std::bad_exception &ex) {
					  std::cout << "bad_exception in file " << xmlFile << " : " << ex.what() << std::endl;
            return NULL;
				}
        catch (...) {
            std::cout << "Unexpected Exception in file " << xmlFile << "\n" ;
            return NULL;
        }

        delete parser;
		return document;
}

template <typename T>
char * ::Dictionary::DictionaryParser<T>::copy_string(char *element)
{
	char *temp_element = (char *)std::malloc((std::strlen(element) + 1)*sizeof(char));
	std::strcpy(temp_element,element);
	return temp_element;
}

template <typename T>
void Dictionary::DictionaryParser<T>::set_state(const Attributes& attrs) 
{
    if (Grammar::DOCUMENT()==this->current_element) {
			current_state = Grammar::document;
			return;
		}
		else if (Grammar::NODE()==this->current_element) {
			current_state = Grammar::node;
			current_node = new Node<T>();
			return;
		}
		else if (Grammar::SECTION()==this->current_element) {
			

			const XMLCh * value = attrs.getValue(XMLString::transcode("type"));
			char *value_transcoded = XMLString::transcode(value);
			current_state = Grammar::section;

			SectionType section_type = Dictionary::get_section_type_by_string(std::string(value_transcoded));
			if (section_type == none) throw std::bad_exception("no type was found in section element");

			current_section = new Section<T>(section_type);
			return;
		}
		else if (Grammar::SENTENCE()==this->current_element) {
			current_state = Grammar::sentence;
			current_sentence = new Sentence<T>();
			return;
		}
}

template <typename T>
void Dictionary::DictionaryParser<T>::end_state(char *element) 
{
    if (Grammar::DOCUMENT()==element) {
			return;
		}
		else if (Grammar::NODE()==element) {
			current_state = Grammar::none;
			document->add_node(current_node);
			return;
		}
		else if (Grammar::SECTION()==element) {
			current_state = Grammar::none;
			current_node->add_section(current_section);
			return;
		}
		else if (Grammar::SENTENCE()==element) {
			current_state = Grammar::none;
			current_section->add_sentence(current_sentence);
			return;
		}
}

template <typename T>
bool Dictionary::DictionaryParser<T>::set_document_properties(char *data) 
{
	if (current_state != Grammar::document) false;

    if (Grammar::FILENAME()==this->current_element) {
			std::string file_name(data);
			document->set_document_name(file_name);
		}
		else if (Grammar::TOTALTERMCOUNT()==this->current_element) {
			long total_term_count = boost::lexical_cast<long,char *>(data);
			document->set_total_term_count(total_term_count);
		}
		else if (Grammar::UNIQUETERMCOUNT()==this->current_element) {
			long unique_term_count = boost::lexical_cast<long,char *>(data);
			document->set_unique_term_count(unique_term_count);
		}
		else if (Grammar::TOTALSENTENCECOUNT()==this->current_element) {
			long total_sentence_count = boost::lexical_cast<long,char *>(data);
			document->set_total_sentence_count(total_sentence_count);
		}
		return true;
}

template <typename T>
bool Dictionary::DictionaryParser<T>::set_node_properties(char *data) 
{
	if (current_state != Grammar::node) false;

    if (Grammar::ID()==this->current_element) {
			long id = boost::lexical_cast<long,char *>(data);
			current_node->set_id(id);
		}
		else if (Grammar::TERM()==this->current_element) {
			std::string term(data);
			current_node->set_term(term);
		}
		else if (Grammar::TOTALTERMFREQUENCY()==this->current_element) {
			long total_term_freq = boost::lexical_cast<long,char *>(data);
			current_node->set_total_term_frequency(total_term_freq);
		}

		return true;
}

template <typename T>
bool Dictionary::DictionaryParser<T>::set_section_properties(char *data) 
{
  	if (current_state != Grammar::section) false;

	  if (Grammar::TERMFREQUENCY()==this->current_element) {
			long freq = boost::lexical_cast<long,char *>(data);
			current_section->set_term_frequency(freq);
		}

		return true;
}

template <typename T>
bool Dictionary::DictionaryParser<T>::set_sentence_properties(char *data) 
{
	if (current_state != Grammar::sentence) false;

    if (Grammar::NUMBER()==this->current_element) {
			int number = boost::lexical_cast<int,char *>(data);
			current_sentence->set_sentence_number(number);
		}
		else if (Grammar::PLACE()==this->current_element) {
			int place = boost::lexical_cast<int,char *>(data);
			current_sentence->set_in_sentence_place(place);
		}
		else if (Grammar::CONNECTINGWORD()==this->current_element) {
			std::string connecting_word(data);
			current_sentence->set_connecting_word(connecting_word);
		}
		else if (Grammar::NEXTID()==this->current_element) {
			try{
				long next_id = boost::lexical_cast<long,char *>(data);
			  current_sentence->set_next_id(next_id);
			}
			catch(boost::bad_lexical_cast &) {}
		}

		return true;
}
#endif
